<?php

namespace StudyBuddy;

/**
 * Wrapped class for working with
 * php's MongoDB classes
 *
 */
class Mongo extends StudyBuddyObject {

    protected static $oMongo;

    /**
     * Mongo connection resource
     *
     * @var object of type Mongo
     */
    protected $conn;

    /**
     * Object MongoDB
     * @var object of type MongoDB
     */
    protected $db;

    /**
     * Name of database
     *
     * @var string
     */
    protected $dbname;

    /**
     * Extra options used during insert and save
     * @var array
     */
    protected $aInsertOption = array('safe' => true);

    /**
     * Prefix for collection names
     * If set to any non-empty string then
     * ALL collections will be prefixed
     * with this string. This option
     * allows to override default collection names
     * used in this program in case the existing
     * database already has collections with same names
     * as in the program.
     *
     * @var string
     */
    protected $prefix = "";

    public function __construct(Ini $oIni) {

        if (!\extension_loaded('mongo')) {
            exit('PHP mongo extension not loaded. Exiting');
        }

        $aOptions = array('connect' => true);
        $aConfig = $oIni->getSection('MONGO');
        d('$aConfig: ' . print_r($aConfig, 1));

        $server = $aConfig['server'];
        /**
         * For Unit testing we define
         * MONGO_DBNAME to be STUDYBUDDY_TEST
         * so that actual database not used during testing
         *
         */
        $this->dbname = (defined('MONGO_DBNAME')) ? constant('MONGO_DBNAME') : $aConfig['db'];

        try {
            $this->conn = new \Mongo($server, $aOptions);
        } catch (\MongoException $e) {
            $err = 'StudyBuddyError unable to connect to Mongo: ' . $e->getMessage();
            e($err);
            throw new DevException($err);
        }

        if (!empty($aConfig['prefix'])) {
            $this->prefix = (string) $aConfig['prefix'];
        }
    }

    /**
     * Getter for $this->dbname
     * @return string name of database used
     */
    public function getDbName() {
        return $this->dbname;
    }

    /**
     * Setter for $this->dbname
     *
     * @param string name Database name
     *
     * @return object $this
     * Enter description here ...
     *
     */
    public function setDbName($name) {

        if (!is_string($name)) {
            throw new \InvalidArgumentException('$name must be a string. Was: ' . gettype($name));
        }

        $this->dbname = $name;

        return $this;
    }

    public function __clone() {
        throw new DevException('cloning Mongo object is not allowed');
    }

    /**
     * By default pass methos to $this->db (MongoDatabase object)
     * @param string $method
     * @param array $args
     */
    public function __call($method, $args) {
        return \call_user_func_array(array($this->getDb(), $method), $args);
    }

    /**
     * Insert array into MongoDB collection
     *
     * @param string $collName name of collection
     *
     * @param array $aValues array of data to insert
     *
     * @param mixed $option option to pass to mongoCollection->insert()
     * this could be bool true for 'safe' but can also be an array
     *
     * @return mixed false on failure or value of _id of inserted doc
     * which can be MongoId Object or string or int, depending if
     * you included value of _id in $aValues or let Mongo generate one
     * By default mongo generates the unique value and it's an object
     * of type MongoId
     */
    public function insertData($collName, array $aValues, $option = true) {
        d('$option: ' . var_export($option, true));

        if (!preg_match('/^[A-Za-z0-9_]+$/', $collName)) {
            throw new \InvalidArgumentException('Invalid collection name: ' . $collName . ' Colletion name can only contain alphanumeric chars and underscores');
        }

        try {
            $coll = $this->getCollection($collName);

            $ret = $coll->insert($aValues, $option);
        } catch (\MongoException $e) {
            e('StudyBuddyError insert() failed: ' . $e->getMessage() . ' values: ' . print_r($aValues, 1) . ' backtrace: ' . $e->getTraceAsString());

            return false;
        }

        d('$ret: ' . $ret . ' $aValues: ' . print_r($aValues, 1));

        return (!empty($aValues['_id'])) ? $aValues['_id'] : false;
    }

    /**
     * @todo this is dangerous!
     * it will replace record with arrValues and will not keep
     * any existing values
     * the better way would be to use $set operator
     * MUST make sure tha arrValues include new values AND CURRENT
     * values that don't have to change. For example, if you
     * only updating 'lastName', make sure arrValues also
     * includes 'firstName' with the current value, otherwise
     * the new object will have only the lastName
     *
     * @param string $collName
     * @param array $arrValues
     * @param string $whereCol
     * @param string $whereVal
     * @param string $strErr2 can be passed to be included in logging
     */
    public function updateCollection($collName, array $arrValues, $whereCol, $whereVal) {
        $strTableName = \filter_var($collName, FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH);
        $strTableName = \str_replace(';', '', $strTableName);
        $strTableName = \addslashes($strTableName);

        $whereCol = \filter_var($whereCol, FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH);
        $whereCol = \str_replace(';', '', $whereCol);
        $whereCol = \addslashes($whereCol);

        $ret = false;

        $coll = $this->getDb()->selectCollection($collName);
        try {
            $ret = $coll->update(array($whereCol => $whereVal), $arrValues, array('fsync' => true));
        } catch (\MongoException $e) {
            e('Unable to update mongo collection ' . $collName . ' ' . $e->getMessage());
        }

        return $ret;
    }

    /**
     * @todo unfinished
     * its supposed to save Serializable object in a special way: inside the
     * array with keys 'o' for object (serialized string) and 's' => true means serialized
     * @param unknown_type $collName
     * @param unknown_type $_id
     * @param Serializable $object
     */
    public function saveObject($collName, $_id, Serializable $object) {
        
    }

    /**
     * Getter for $this->db
     *
     * @return object of type MongoDB
     */
    public function getDb() {
        return $this->conn->selectDB($this->dbname);
    }

    /**
     * Return Mongo Collection object from default database
     * if you need to select collection from different database, then
     * you should use getMongo->selectCollection($db, $collName)
     *
     * @param string $collName name of collection
     *
     * @return object of type MongoCollection
     */
    public function getCollection($collName) {
        if (!\is_string($collName)) {
            throw new \InvalidArgumentException('Param $collName must be a string. was: ' . gettype($collName));
        }

        d('$collName: ' . $collName);

        $coll = defined('StudyBuddy\Mongo\\' . $collName) ? \constant('StudyBuddy\Mongo\\' . $collName) : \constant('StudyBuddy\My\\' . $collName);

        return $this->conn->selectCollection($this->dbname, $this->prefix . $coll);
    }

    /**
     * Getter for prefix
     *
     * @return string by default prefix is an empty String
     * which is perfectly fine
     *
     */
    public function getPrefix() {
        return $this->prefix;
    }

    /**
     * Setter for $this->prefix
     *
     * @param string $prefix
     */
    public function setPrefix($prefix) {
        $this->prefix = (string) $prefix;
    }

    /**
     * Alias of getCollection()
     * This is the same name as in php's MongoDB class
     *
     * @param string $collName
     */
    public function selectCollection($collName) {
        return $this->getCollection($collName);
    }

    /**
     * Magic getter to simplify selecting collection
     * Same as getCollection() but simpler code
     * $this->oRegistry->Mongo->USERS
     * the same as $this->oRegistry->Mongo->getCollection('USERS')
     *
     * @param string $name
     *
     * @return object of type MongoCollection
     */
    public function __get($name) {
        return $this->getCollection($name);
    }

    /**
     * Getter for $this->conn
     *
     * @return object Mongo
     */
    public function getMongo() {
        return $this->conn;
    }

}
